/********************************************************************************
 * Project Name		[JavaEE_Web]
 * File Name     	[FileUploadServlet.java]
 * Creation Date 	[2015-01-01]
 * 
 * Copyright© ge.y.yang@gmail.com All Rights Reserved
 * 
 * Work hard, play harder, think big and keep fit
 ********************************************************************************/
package file.upload.commons;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

/**
 * 在 api.FileUploadServlet 中配置了 @MultipartConfig 时此Servlet无法正常工作<br/>
 * 
 * @author 不落的太阳(Sean Yang aka ShortPeace)
 * @version 1.0
 * @since jdk 1.8
 * 
 */
@WebServlet(name = "FileUploadServlet", urlPatterns = "/commonFileUpload")
public class FileUploadServlet extends HttpServlet {

	private static final long serialVersionUID = 9185933886018395448L;

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		Properties properties = (Properties) request.getServletContext().getAttribute("fileConfig");
		List<String> extensions = Arrays.asList(properties.getProperty(FileUploadConstants.EXTENSIONS).split(","));
		long maxFileSize = Long.parseLong(properties.getProperty(FileUploadConstants.FILE_MAX_SIZE));
		long totalMaxSize = Long.parseLong(properties.getProperty(FileUploadConstants.TOTAL_MAX_SIZE));
		String tempDirPath = properties.getProperty(FileUploadConstants.TEMP_DIR_PATH);

		// 保存需要上传的FileItem, key: 文件待存放路径, value: 对应的FileItem对象
		Map<String, FileItem> fileItemsMap = new HashMap<>();

		// 1. 解析请求, 得到FileItems集合
		ServletFileUpload fileUpload = this.getServletFileUpload(request, maxFileSize, totalMaxSize, tempDirPath);

		// 判断提交上来的数据是否是上传表单的数据
		if (!ServletFileUpload.isMultipartContent(request)) {
			return;
		}

		List<FileItem> fileItems = null;
		try {
			fileItems = fileUpload.parseRequest(request);

			// 2. 构建FileBean集合, 同时填充fileItemsMap
			List<FileBean> fileBeans = buildFileBeans(fileItems, fileItemsMap);

			// 3. 校验扩展名
			validateExtensions(fileBeans, extensions);

			// 4. 校验文件大小, 由commons.fileupload负责校验

			// 5. 上传文件
			uploadFiles(fileItemsMap);

			// 6. 保存Bean到Database
			// saveBeans(fileBeans);

			// 7. 删除临时文件夹中的临时文件
		} catch (Exception e) {
			e.printStackTrace();
			request.setAttribute("message", e.getMessage());
		}
		request.getRequestDispatcher("/jsp/fileupload/commons/upload.jsp").forward(request, response);
	}

	/**
	 * 获得ServletFileUpload
	 * 
	 * @param request
	 * @param maxFileSize
	 * @param totalMaxSize
	 * @return
	 */
	private ServletFileUpload getServletFileUpload(HttpServletRequest request, long maxFileSize, long totalMaxSize,
			String tempDirPath) {
		DiskFileItemFactory factory = new DiskFileItemFactory();
		factory.setSizeThreshold(1024 * 500);

		File tempDir = new File(tempDirPath);
		if (!tempDir.exists()) {
			tempDir.mkdirs();
		}
		factory.setRepository(tempDir);

		ServletFileUpload fileUpload = new ServletFileUpload(factory);

		// 设置单个文件最大字节数
		fileUpload.setFileSizeMax(maxFileSize);
		// 设置总共允许上传的最大字节数
		fileUpload.setSizeMax(totalMaxSize);
		// 设置编码
		fileUpload.setHeaderEncoding("UTF-8");
		// 设置进度监听器
		fileUpload.setProgressListener(new ProgressListener() {
			@Override
			public void update(long bytesRead, long contentLength, int item) {
				System.out.println("第" + (item + 1) + "个文件上传, 大小为  " + contentLength + ", 已经上传 " + bytesRead);
			}
		});

		return fileUpload;
	}

	/**
	 * 利用传入的FileItem集合, 构建FileBean的集合, 同时填充fileItemsMap<br/>
	 * 
	 * 构建过程<br/>
	 * 1. 遍历FileItem集合, 得到每一个文件描述域, 把文件描述域的name[desc1, desc2...]作为key,
	 * 把文件描述域的值[页面输入值]作为value存入一个Map<br/>
	 * 
	 * 2. 遍历FileItem集合, 得到每一个FileItem对象,
	 * 通过每一个FileItem对象的fieldName[fiel1,file2...]构建出desc1, desc2...,
	 * 然后从第一步构建的Map中得到对应的desc value, 构建出一个个的FileBean对象<br/>
	 * 
	 * 3. 将第二步构建出的FileBean加入到集合中, 并填充fileItemsMap<br/>
	 * 
	 * @param fileItems
	 * @param itemsMap
	 * @return
	 * @throws IOException
	 */
	private List<FileBean> buildFileBeans(List<FileItem> fileItems, Map<String, FileItem> fileItemsMap)
			throws IOException {
		List<FileBean> fileBeans = new ArrayList<>();

		// 保存desc域的信息
		Map<String, String> descs = new HashMap<>();

		for (FileItem fileItem : fileItems) {
			if (fileItem.isFormField()) {
				String filedName = fileItem.getFieldName();
				String fieldValue = fileItem.getString("UTF-8");
				// 如果用户未填写描述, 则使用默认描述
				if (fieldValue != null && !"".equals(fieldValue.trim())) {
				} else {
					fieldValue = filedName;
				}
				descs.put(filedName, fieldValue);
			}
		}

		for (FileItem fileItem : fileItems) {
			FileBean bean = null;
			if (!fileItem.isFormField()) {
				String fieldName = fileItem.getFieldName();
				String descName = "desc" + fieldName.substring(fieldName.length() - 1);
				String fileDesc = descs.get(descName);

				String fileName = fileItem.getName();
				// 如果为添加文件, 则不进行上传
				if (fileName != null && !"".equals(fileName.trim())) {
					String filePath = getFilePath(fileName);
					bean = new FileBean(fileName, filePath, fileDesc);
					fileBeans.add(bean);
					fileItemsMap.put(bean.getFilePath(), fileItem);
				}
			}
		}
		return fileBeans;
	}

	/**
	 * 根据指定的文件名生成随机的文件名<br/>
	 * 
	 * 1. 生成文件的扩展名和指定文件的扩展名一致<br/>
	 * 2. 利用ServletContext的getRealPath方法获取的绝对路径<br/>
	 * 3. 利用了UUID来生成随机的文件名<br/>
	 * 
	 * @param fileName
	 * @return
	 */
	private String getFilePath(String fileName) {
		String filePath = ((Properties) this.getServletContext().getAttribute("fileConfig"))
				.getProperty(FileUploadConstants.FILE_PATH);
		File dir = new File(filePath);
		if (!dir.exists()) {
			dir.mkdirs();
		}
		return filePath + "/" + UUID.randomUUID() + "_" + fileName;
	}

	/**
	 * 验证扩展名是否合法
	 * 
	 * @param fileBeans
	 * @param extensions
	 */
	private void validateExtensions(List<FileBean> fileBeans, List<String> extensions) {
		for (FileBean bean : fileBeans) {
			String fileName = bean.getFileName();
			String fileExtention = fileName.substring(fileName.indexOf(".") + 1);

			if (!extensions.contains(fileExtention)) {
				throw new RuntimeException(fileName + "文件扩展名不合法");
			}
		}
	}

	/**
	 * 文件上传
	 * 
	 * @param fileItemsMap
	 * @throws IOException
	 */
	private void uploadFiles(Map<String, FileItem> fileItemsMap) throws IOException {
		for (Map.Entry<String, FileItem> entry : fileItemsMap.entrySet()) {
			String filePath = entry.getKey();
			FileItem fileItem = entry.getValue();

			this.uploadFiles(filePath, fileItem.getInputStream());
		}
	}

	/**
	 * 具体的文件上传方法
	 * 
	 * @param filePath
	 * @param inputStream
	 * @throws IOException
	 */
	private void uploadFiles(String filePath, InputStream inputStream) throws IOException {
		OutputStream outputStreamm = new FileOutputStream(filePath);
		byte[] buffer = new byte[2048];
		int length = 0;
		while ((length = inputStream.read(buffer)) != -1) {
			outputStreamm.write(buffer, 0, length);
		}
		inputStream.close();
		outputStreamm.close();
	}

}
